iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
佛心分享-SideProject30

30 天開發一款真正能每天使用的散步 App系列 第 12

推薦路線(3)——判斷是否可以開始散步

  • 分享至 

  • xImage
  •  

如果人在台北,能不能先規劃週末去台南玩的散步路線?

這是一個很常見的情境。例如使用者可能會想搜尋:

  • 台南車站附近有什麼散步路線?
  • 能不能經過公園?
  • 走半小時會經過哪些地方?

解決這個問題的核心,並非限制使用者的操作,而是將體驗拆解為兩種動態模式:「規劃模式」「散步模式」

當搜尋的起點或目標路線距離裝置目前位置過遠時,系統會自動進入「規劃模式」;反之,若裝置已靠近路線,則自動切換或允許開啟「散步模式」。

規劃模式

當裝置距離目標路線尚有一段距離時,代表使用者目前並沒有要「立刻出發」。此時 App 會鎖定在規劃模式。

在規劃模式下,使用者依然可以:

  • 查看路線
  • 查看預估距離與時間
  • 收藏路線
  • 導航到起點

但不會顯示「開始散步」的按鈕,因為目前還沒有真正抵達這條路線。

只有當裝置已經靠近推薦路線時,才能夠點擊開始散步的按鈕。

散步模式

當使用者抵達推薦路線附近後,才允許點擊「開始散步」進入散步模式。

不過還有一個問題:

系統要怎麼知道使用者真的已經抵達推薦路線?

怎麼判斷裝置是否已經靠近路線?

直覺的作法是比較「目前位置」與「路線起點」的距離,不過思考了一下就發現行不通。

假設推薦的是一條 3 公里的河濱步道,有些人可能會從起點開始,也有人會直接從另一個入口進入。若要求一定要回到起點,即使人已經站在步道旁邊,也可能因為距離起點太遠,而始終無法開始散步。

因此判斷標準不應該是「距離起點有多遠」,而是「距離整條推薦路線的最短距離有多遠」。

將 Polyline 解碼成 Route Points

Google Routes 回傳的路線,其實不是一串經緯度,而是一段 Encoded Polyline:

abcedfghijk......

這段字串無法直接繪製,需要先進行解碼以取得座標點陣列:

Google Routes
      ↓
Encoded Polyline
      ↓
decodePolyline()
      ↓
Route Points[{lat, lng}, ...]

解碼完成後會得到一系列的 Route Points,每個點都代表路線折線上的頂點座標。

利用 Route Points 估算最短距離

有了 Route Points 後,最直接的方式是遍歷所有點,找出距離目前裝置最近的頂點。

使用者目前的位置
                ●
Route Points    │ (最短距離)
●────●────●────●────●
               ↑
         最近的 Route Point

基礎實作:

// 計算兩經緯度之間的實際地面距離(Haversine Formula),單位:公尺
function distanceMeters(pt1, pt2) {
  const R = 6371000; // 地球平均半徑 (m)
  const dLat = (pt2.lat - pt1.lat) * (Math.PI / 180);
  const dLng = (pt2.lng - pt1.lng) * (Math.PI / 180);
  
  const a =
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(pt1.lat * (Math.PI / 180)) *
      Math.cos(pt2.lat * (Math.PI / 180)) *
      Math.sin(dLng / 2) *
      Math.sin(dLng / 2);

  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  return R * c;
}

// 遍歷所有 Route Points 找出最近距離
function distanceToNearestRoutePoint(deviceLocation, routePoints) {
  if (!routePoints || routePoints.length === 0) return Infinity;

  let nearest = Infinity;

  for (const point of routePoints) {
    nearest = Math.min(
      nearest,
      distanceMeters(deviceLocation, point)
    );
  }

  return nearest;
}

// 判斷是否滿足開始條件
const canStart =
  distanceToNearestRoutePoint(deviceLocation, route.points) <= START_DISTANCE;

進階優化:解決頂點過稀問題(Point-to-Segment)

僅計算「點到頂點」的距離在多數情況下可行,但在極長直線路段(如 1 公里無轉彎的大道)上,Google Polyline 可能只會留頭尾兩個點。若使用者剛好站在這條路的中段,距離兩端頂點都很遠,系統就會誤判。

若要追求更嚴謹的判定,可引入幾何庫(如 Turf.js),計算使用者位置到路線線段(Line Segment)的垂直最短距離:

import pointToLineDistance from '@turf/point-to-line-distance';
import { point, lineString } from '@turf/helpers';

function distanceToNearestRoute(deviceLocation, routePoints) {
  const userPt = point([deviceLocation.lng, deviceLocation.lat]);
  const lineCoords = routePoints.map(p => [p.lng, p.lat]);
  const routeLine = lineString(lineCoords);

  // 計算點到折線的最短垂直距離 (回傳 km 轉為 m)
  const distanceKm = pointToLineDistance(userPt, routeLine, { units: 'kilometers' });
  return distanceKm * 1000;
}


上一篇
推薦路線(2)——能走的路很多,該選哪一條?
系列文
30 天開發一款真正能每天使用的散步 App12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言